// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/resource_coordinator/tab_activity_watcher.h"

#include <memory>

#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h"
#include "chrome/browser/resource_coordinator/tab_manager_features.h"
#include "chrome/browser/resource_coordinator/tab_metrics_event.pb.h"
#include "chrome/browser/resource_coordinator/time.h"
#include "chrome/browser/resource_coordinator/utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_ukm_test_helper.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/web_contents_tester.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"

using ukm::builders::TabManager_TabMetrics;
using ukm::builders::TabManager_Background_ForegroundedOrClosed;

namespace resource_coordinator {
namespace {

const char* kTabMetricsEntryName = TabManager_TabMetrics::kEntryName;
const char* kFOCEntryName =
    TabManager_Background_ForegroundedOrClosed::kEntryName;

// The default metric values for a tab.
const UkmMetricMap kBasicMetricValues({
    {TabManager_TabMetrics::kHasBeforeUnloadHandlerName, 0},
    {TabManager_TabMetrics::kHasFormEntryName, 0},
    {TabManager_TabMetrics::kIsPinnedName, 0},
    {TabManager_TabMetrics::kKeyEventCountName, 0},
    {TabManager_TabMetrics::kNavigationEntryCountName, 1},
    {TabManager_TabMetrics::kSiteEngagementScoreName, 0},
    {TabManager_TabMetrics::kTouchEventCountName, 0},
    {TabManager_TabMetrics::kWasRecentlyAudibleName, 0},
    // TODO(michaelpg): Test TabManager_TabMetrics::kMouseEventCountName.
    // Depending on the test environment, the browser may receive mouse events
    // from the mouse cursor during tests, so we currently don't check this
    // metric.
});

// These parameters don't affect logging.
const bool kCheckNavigationSuccess = true;
const int64_t kIdShift = 1 << 16;

}  // namespace

// Tests UKM entries generated by TabActivityWatcher/TabMetricsLogger as tabs
// are backgrounded and foregrounded.
// Modeled after the TabActivityWatcherTest unit tests, these browser tests
// focus on end-to-end testing from the first browser launch onwards, verifying
// that window and browser commands are really triggering the paths that lead
// to UKM logs.
class TabActivityWatcherTest : public InProcessBrowserTest {
 public:
  TabActivityWatcherTest(const TabActivityWatcherTest&) = delete;
  TabActivityWatcherTest& operator=(const TabActivityWatcherTest&) = delete;

 protected:
  TabActivityWatcherTest() {
    scoped_feature_list_.InitAndEnableFeatureWithParameters(
        features::kTabRanker,
        {{"disable_background_log_with_TabRanker", "false"}});
  }

  // TabActivityWatcherTest:
  void PreRunTestOnMainThread() override {
    InProcessBrowserTest::PreRunTestOnMainThread();
    ukm_entry_checker_ = std::make_unique<UkmEntryChecker>();
    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
    test_urls_ = {embedded_test_server()->GetURL("/title1.html"),
                  embedded_test_server()->GetURL("/title2.html"),
                  embedded_test_server()->GetURL("/title3.html")};
    // Browser created in BrowserMain() shouldn't result in a background tab
    // being logged.
    EXPECT_EQ(0u, ukm_entry_checker_->NumEntries(kTabMetricsEntryName));
  }

  void TearDown() override {
    EXPECT_EQ(0,
              ukm_entry_checker_->NumNewEntriesRecorded(kTabMetricsEntryName));
    InProcessBrowserTest::TearDown();
  }

 protected:
  void ExpectNewForegroundedEntry(const GURL& url) {
    // TODO(michaelpg): Add an interactive_ui_test to test MRU metrics since
    // they can be affected by window activation.
    UkmMetricMap expected_metrics = {
        {TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 1},
    };
    ukm_entry_checker_->ExpectNewEntry(kFOCEntryName, url, expected_metrics);
  }

  void ExpectNewClosedEntry(const GURL& url) {
    UkmMetricMap expected_metrics = {
        {TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 0},
    };
    ukm_entry_checker_->ExpectNewEntry(kFOCEntryName, url, expected_metrics);
  }

  // Uses test_ukm_recorder_ to check new event metrics including:
  // (1) the number of UkmEntry with given event_name should be equal to |size|.
  // (2) the newest entry should have source_url equal to |url|.
  // (3) the newest entry should have source_id equal to |source_id| if
  //     |source_id| is not 0 (skip for the case of 0).
  // (4) the newest entry should contain all metrics in |expected_metrics|.
  // Also returns the source_id of the newest entry.
  ukm::SourceId ExpectNewEntryWithSourceId(const GURL& url,
                                           const std::string& event_name,
                                           size_t num_entries,
                                           const UkmMetricMap& expected_metrics,
                                           ukm::SourceId source_id = 0) {
    const std::vector<const ukm::mojom::UkmEntry*> entries =
        test_ukm_recorder_->GetEntriesByName(event_name);
    // Check size.
    EXPECT_EQ(entries.size(), num_entries);
    const ukm::mojom::UkmEntry* entry = entries.back();
    // Check source_url.
    test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, url);
    // Check source_id.
    if (source_id != 0) {
      EXPECT_EQ(source_id, entry->source_id);
    }
    // Check expected_metrics.
    for (const auto& metric : expected_metrics) {
      test_ukm_recorder_->ExpectEntryMetric(entry, metric.first,
                                            *metric.second);
    }

    return entry->source_id;
  }

  // Gets the latest metric value from the event with given |metric_name|.
  int64_t GetLatestMetricValue(const std::string& event_name,
                               const std::string& metric_name) {
    const std::vector<const ukm::mojom::UkmEntry*> entries =
        test_ukm_recorder_->GetEntriesByName(event_name);
    // Check nonemptiness.
    EXPECT_FALSE(entries.empty());

    return *(test_ukm_recorder_->GetEntryMetric(entries.back(), metric_name));
  }

  // Logs tab@i.
  void LogTabFeaturesAt(const int i) {
    resource_coordinator::LifecycleUnit* lifecycle_unit =
        GetTabLifecycleUnitSource()->GetTabLifecycleUnit(
            browser()->tab_strip_model()->GetWebContentsAt(i));

    std::vector<resource_coordinator::LifecycleUnit*> lifecycle_unit_vec = {
        lifecycle_unit};

    // Calling LogAndMaybeSortLifecycleUnitWithTabRanker will log the
    // TabFeatures.
    TabActivityWatcher::GetInstance()
        ->LogAndMaybeSortLifecycleUnitWithTabRanker(&lifecycle_unit_vec);
  }

  std::vector<GURL> test_urls_;
  std::unique_ptr<UkmEntryChecker> ukm_entry_checker_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

class TabActivityWatcherTestWithBackgroundLogDisabled
    : public TabActivityWatcherTest {
 public:
  TabActivityWatcherTestWithBackgroundLogDisabled() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kTabRanker,
        {{"disable_background_log_with_TabRanker", "true"}});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Tests calculating tab scores using the Tab Ranker.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTestWithBackgroundLogDisabled,
                       CalculateReactivationScore) {
  // Use test clock so tabs have non-zero backgrounded times.
  base::SimpleTestTickClock test_clock;
  ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing(&test_clock);
  test_clock.Advance(base::Minutes(1));

  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[0], ui::PAGE_TRANSITION_LINK));
  test_clock.Advance(base::Minutes(1));

  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  test_clock.Advance(base::Minutes(1));

  // A background tab is scored successfully.
  absl::optional<float> background_score =
      TabActivityWatcher::GetInstance()->CalculateReactivationScore(
          browser()->tab_strip_model()->GetWebContentsAt(1));
  EXPECT_TRUE(background_score.has_value());

  // Foreground tabs are not modeled by the tab ranker and should not be scored.
  absl::optional<float> foreground_score =
      TabActivityWatcher::GetInstance()->CalculateReactivationScore(
          browser()->tab_strip_model()->GetWebContentsAt(0));
  EXPECT_FALSE(foreground_score.has_value());

  CloseBrowserSynchronously(browser());
}

// Tests TabMetrics UKMs logged by creating and switching between tabs.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, SwitchTabs) {
  const GURL kTabUrls[] = {
      GURL(),  // "about:blank" tab doesn't have a UKM source.
      test_urls_[0], test_urls_[1],
  };

  EXPECT_EQ(0u, ukm_entry_checker_->NumEntries(kTabMetricsEntryName));

  UkmMetricMap expected_metrics = kBasicMetricValues;

  // Adding a new foreground tab logs the previously active tab.
  ASSERT_TRUE(AddTabAtIndex(1, kTabUrls[1], ui::PAGE_TRANSITION_LINK));
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kTabUrls[0],
                                       expected_metrics);
  }

  ASSERT_TRUE(AddTabAtIndex(2, kTabUrls[2], ui::PAGE_TRANSITION_LINK));
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kTabUrls[1],
                                       expected_metrics);
  }

  // Switching to another tab logs the previously active tab.
  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kTabUrls[2],
                                       expected_metrics);
    ExpectNewForegroundedEntry(kTabUrls[0]);
  }

  browser()->tab_strip_model()->ActivateTabAt(
      1, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kTabUrls[0],
                                       expected_metrics);
    ExpectNewForegroundedEntry(kTabUrls[1]);
  }

  // Closing the window doesn't log more TabMetrics UKMs (tested in TearDown()).
  CloseBrowserSynchronously(browser());
  {
    SCOPED_TRACE("");
    ExpectNewClosedEntry(kTabUrls[2]);
    ExpectNewClosedEntry(kTabUrls[0]);
  }
}

// Tests that switching between multiple windows doesn't affect TabMetrics UKMs.
// This is a sanity check; window activation shouldn't make any difference to
// what we log. If we needed to actually test different behavior based on window
// focus, we would run these tests in interactive_ui_tests.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, SwitchWindows) {
  Browser* browser_2 = CreateBrowser(browser()->profile());
  EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kTabMetricsEntryName));

  EXPECT_EQ(
      AddTabAtIndexToBrowser(browser(), 1, test_urls_[0],
                             ui::PAGE_TRANSITION_LINK, kCheckNavigationSuccess),
      kCheckNavigationSuccess);
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, GURL(),
                                       kBasicMetricValues);
  }

  EXPECT_EQ(
      AddTabAtIndexToBrowser(browser_2, 1, test_urls_[1],
                             ui::PAGE_TRANSITION_LINK, kCheckNavigationSuccess),
      kCheckNavigationSuccess);
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, GURL(),
                                       kBasicMetricValues);
  }

  browser()->window()->Activate();
  browser_2->window()->Activate();
  EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kTabMetricsEntryName));

  // Closing each window doesn't log more TabMetrics UKMs.
  CloseBrowserSynchronously(browser_2);
  CloseBrowserSynchronously(browser());
}

// Tests page with a beforeunload handler.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, BeforeUnloadHandler) {
  // Navigate to a page with a beforeunload handler.
  GURL url(embedded_test_server()->GetURL("/beforeunload.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Log metrics for the first tab by switching to a new tab.
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[0], ui::PAGE_TRANSITION_LINK));
  UkmMetricMap expected_metrics = kBasicMetricValues;
  expected_metrics[TabManager_TabMetrics::kHasBeforeUnloadHandlerName] = 1;
  expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName] = 2;
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, url,
                                       expected_metrics);
  }

  // Sanity check: the new tab doesn't have a beforeunload handler.
  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
                                       kBasicMetricValues);
  }
}

// Tests events logged when dragging a tab between browsers.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, TabDrag) {
  // This test will navigate 3 tabs.
  const GURL kBrowserStartUrl = test_urls_[0];
  const GURL kBrowser2StartUrl = test_urls_[1];
  const GURL kDraggedTabUrl = test_urls_[2];

  Browser* browser_2 = CreateBrowser(browser()->profile());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kBrowserStartUrl));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser_2, kBrowser2StartUrl));

  // Adding a tab backgrounds the original tab in the window.
  EXPECT_EQ(
      AddTabAtIndexToBrowser(browser(), 1, kDraggedTabUrl,
                             ui::PAGE_TRANSITION_LINK, kCheckNavigationSuccess),
      kCheckNavigationSuccess);
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics_1 = kBasicMetricValues;
    expected_metrics_1[TabManager_TabMetrics::kNavigationEntryCountName] = 2;
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kBrowserStartUrl,
                                       expected_metrics_1);
  }

  // "Drag" the new tab out of its browser.
  content::WebContents* dragged_contents =
      browser()->tab_strip_model()->GetWebContentsAt(1);
  std::unique_ptr<content::WebContents> owned_dragged_contents =
      browser()->tab_strip_model()->DetachWebContentsAtForInsertion(1);
  dragged_contents->WasHidden();
  // The other tab in the browser is now foregrounded.
  ExpectNewForegroundedEntry(kBrowserStartUrl);

  // "Drop" the tab into the other browser. This requires showing and
  // reactivating the tab, but to the user, it never leaves the foreground, so
  // we don't log a foregrounded event for it.
  browser_2->tab_strip_model()->InsertWebContentsAt(
      1, std::move(owned_dragged_contents), TabStripModel::ADD_NONE);
  dragged_contents->WasShown();
  browser_2->tab_strip_model()->ActivateTabAt(
      1, {TabStripModel::GestureType::kOther});
  EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kFOCEntryName));

  // The first tab in this window was backgrounded when the new one was
  // inserted.
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics_2 = kBasicMetricValues;
    expected_metrics_2[TabManager_TabMetrics::kNavigationEntryCountName] = 2;
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, kBrowser2StartUrl,
                                       expected_metrics_2);
  }

  // Closing the window with 2 tabs means we log the backgrounded tab as closed.
  CloseBrowserSynchronously(browser_2);
  ExpectNewClosedEntry(kBrowser2StartUrl);

  CloseBrowserSynchronously(browser());
  EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kTabMetricsEntryName));
  EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kFOCEntryName));
}

class TabActivityWatcherTestWithBackgroundLogEnabled
    : public TabActivityWatcherTest {
 public:
  TabActivityWatcherTestWithBackgroundLogEnabled() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kTabRanker,
        {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
         {"disable_background_log_with_TabRanker", "false"}});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Tests discarded tab is recorded correctly.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTestWithBackgroundLogEnabled,
                       DiscardedTabGetsPreviousSourceId) {
  ukm::SourceId ukm_source_id_for_tab_0 = 0;
  ukm::SourceId ukm_source_id_for_tab_1 = 0;

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_urls_[0]));
  EXPECT_EQ(0u, ukm_entry_checker_->NumEntries(kTabMetricsEntryName));

  // Adding a new foreground tab logs the previously active tab.
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK));
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(
        kTabMetricsEntryName, test_urls_[0],
        {{TabManager_TabMetrics::kNavigationEntryCountName, 2}});

    ukm_source_id_for_tab_0 = ExpectNewEntryWithSourceId(
        test_urls_[0], kTabMetricsEntryName, 1,
        {{TabManager_TabMetrics::kNavigationEntryCountName, 2}});
  }

  // Discard the first tab.
  content::WebContents* first_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);
  resource_coordinator::GetTabLifecycleUnitSource()
      ->GetTabLifecycleUnitExternal(first_contents)
      ->DiscardTab(LifecycleUnitDiscardReason::URGENT);

  // Logs tab@0.
  LogTabFeaturesAt(0);
  {
    SCOPED_TRACE("");
    // tab feature of tab@0 should be logged correctly.
    UkmMetricMap expected_tab_feature_values = kBasicMetricValues;
    expected_tab_feature_values[TabManager_TabMetrics::kMRUIndexName] = 1;
    expected_tab_feature_values
        [TabManager_TabMetrics::kNumReactivationBeforeName] = 0;
    expected_tab_feature_values[TabManager_TabMetrics::kTotalTabCountName] = 2;
    expected_tab_feature_values[TabManager_TabMetrics::kWindowTabCountName] = 2;
    expected_tab_feature_values[TabManager_TabMetrics::kLabelIdName] =
        2 * kIdShift;
    expected_tab_feature_values[TabManager_TabMetrics::kQueryIdName] =
        1 * kIdShift;
    expected_tab_feature_values
        [TabManager_TabMetrics::kNavigationEntryCountName] = 2;

    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
                                       expected_tab_feature_values);
    ExpectNewEntryWithSourceId(test_urls_[0], kTabMetricsEntryName, 2,
                               expected_tab_feature_values,
                               ukm_source_id_for_tab_0);
  }

  // Switching to first tab logs a forgrounded event for test_urls_[0]
  // and a backgrounded event for test_urls_[1].
  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[1],
                                       kBasicMetricValues);

    ukm_source_id_for_tab_1 = ExpectNewEntryWithSourceId(
        test_urls_[1], kTabMetricsEntryName, 3, kBasicMetricValues);

    ExpectNewEntryWithSourceId(
        test_urls_[0], kFOCEntryName, 1,
        {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 1},
         {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 1}},
        ukm_source_id_for_tab_0);
  }

  // Discard the second tab.
  content::WebContents* second_content =
      browser()->tab_strip_model()->GetWebContentsAt(1);
  resource_coordinator::GetTabLifecycleUnitSource()
      ->GetTabLifecycleUnitExternal(second_content)
      ->DiscardTab(LifecycleUnitDiscardReason::URGENT);

  CloseBrowserSynchronously(browser());
  {
    SCOPED_TRACE("");

    ExpectNewEntryWithSourceId(
        test_urls_[1], kFOCEntryName, 2,
        {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 0},
         {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 1}},
        ukm_source_id_for_tab_1);
  }
}

// Tests that all window metrics are logged with correct value which are
// different from their default values in TabFeatures.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, AllWindowMetricsArePopulated) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_urls_[0]));

  // Adding a new foreground tab logs the previously active tab.
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK));
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = {
        {"WindowShowState", metrics::WindowMetricsEvent::SHOW_STATE_NORMAL},
        {"WindowTabCount", 2},
        {"WindowType", metrics::WindowMetricsEvent::TYPE_TABBED},
    };
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
                                       expected_metrics);
  }
}

class TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab
    : public TabActivityWatcherTest {
 public:
  TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab() {
    // Set Feature params for this test.
    // (1) background log is disabled, so that only query time logging and
    // corresponding labels should be logged.
    // (2) number of oldest tabs to log is set to 1, so that only 1 tab should
    // be logged.
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kTabRanker,
        {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
         {"disable_background_log_with_TabRanker", "true"}});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Test the query time logging is correct.
IN_PROC_BROWSER_TEST_F(
    TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab,
    LogOldestNTabFeatures) {
  // Use test clock so tabs have non-zero backgrounded times.
  base::SimpleTestTickClock test_clock;
  ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing(&test_clock);
  test_clock.Advance(base::Minutes(1));

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_urls_[0]));
  // Insert the tab@1.
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK));
  test_clock.Advance(base::Minutes(1));
  // Insert the tab@2.
  ASSERT_TRUE(AddTabAtIndex(2, test_urls_[2], ui::PAGE_TRANSITION_LINK));
  test_clock.Advance(base::Minutes(1));
  // Activate tab@0.
  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  test_clock.Advance(base::Minutes(1));

  // TabManager_TabMetrics should not have been logged yet.
  EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kTabMetricsEntryName));

  // TabManager_Background_ForegroundedOrClosed should have been logged when
  // tab@0 was activated.
  ukm_entry_checker_->ExpectNewEntry(
      kFOCEntryName, test_urls_[0],
      {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 1},
       {TabManager_Background_ForegroundedOrClosed::kLabelIdName, 0},
       {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 0}});

  // Logs tab@1.
  LogTabFeaturesAt(1);
  {
    SCOPED_TRACE("");
    // tab feature of tab@1 should be logged correctly.
    UkmMetricMap expected_tab_feature_values = kBasicMetricValues;
    expected_tab_feature_values[TabManager_TabMetrics::kMRUIndexName] = 2;
    expected_tab_feature_values
        [TabManager_TabMetrics::kNumReactivationBeforeName] = 0;
    expected_tab_feature_values[TabManager_TabMetrics::kTotalTabCountName] = 3;
    expected_tab_feature_values[TabManager_TabMetrics::kWindowTabCountName] = 3;
    expected_tab_feature_values[TabManager_TabMetrics::kLabelIdName] =
        2 * kIdShift;
    expected_tab_feature_values[TabManager_TabMetrics::kQueryIdName] =
        1 * kIdShift;

    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[1],
                                       expected_tab_feature_values);
  }

  // Reactivate tab@1 should log a ForegroundedOrClosed event with LabelId as
  // label_id_1.
  browser()->tab_strip_model()->ActivateTabAt(
      1, {TabStripModel::GestureType::kOther});
  test_clock.Advance(base::Minutes(1));
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(
        kFOCEntryName, test_urls_[1],
        {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 1},
         {TabManager_Background_ForegroundedOrClosed::kLabelIdName,
          2 * kIdShift},
         {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 0}});
  }

  // Logs tab@2.
  LogTabFeaturesAt(2);
  {
    SCOPED_TRACE("");
    // tab feature of tab@2 should be logged correctly.
    UkmMetricMap expected_tab_feature_values = kBasicMetricValues;
    expected_tab_feature_values[TabManager_TabMetrics::kMRUIndexName] = 2;
    expected_tab_feature_values
        [TabManager_TabMetrics::kNumReactivationBeforeName] = 0;
    expected_tab_feature_values[TabManager_TabMetrics::kTotalTabCountName] = 3;
    expected_tab_feature_values[TabManager_TabMetrics::kWindowTabCountName] = 3;
    expected_tab_feature_values[TabManager_TabMetrics::kLabelIdName] =
        4 * kIdShift;
    expected_tab_feature_values[TabManager_TabMetrics::kQueryIdName] =
        3 * kIdShift;

    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[2],
                                       expected_tab_feature_values);
  }

  // No ForegroundedOrClosed event is logged for tab@1 because it's foregrounded
  CloseBrowserSynchronously(browser());
  {
    SCOPED_TRACE("");
    // No ForegroundedOrClosed event is logged for tab@1 because it was
    // foregrounded. The event should be logged for tab@0 and tab@2.

    // tab@2
    ukm_entry_checker_->ExpectNewEntry(
        kFOCEntryName, test_urls_[2],
        {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 0},
         {TabManager_Background_ForegroundedOrClosed::kLabelIdName,
          4 * kIdShift},
         {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 0}});

    // tab@0
    ukm_entry_checker_->ExpectNewEntry(
        kFOCEntryName, test_urls_[0],
        {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 0},
         {TabManager_Background_ForegroundedOrClosed::kLabelIdName, 0},
         {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 0}});
  }
}

// Tests label id is recorded correctly for discarded tabs.
IN_PROC_BROWSER_TEST_F(
    TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab,
    DiscardedTabGetsCorrectLabelId) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_urls_[0]));
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK));
  // No TabMetrics events are logged till now.
  EXPECT_EQ(0u, ukm_entry_checker_->NumEntries(kTabMetricsEntryName));

  // Logs tab@0.
  LogTabFeaturesAt(0);
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = kBasicMetricValues;
    expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName] = 2;
    expected_metrics[TabManager_TabMetrics::kLabelIdName] = 2 * kIdShift;
    expected_metrics[TabManager_TabMetrics::kQueryIdName] = 1 * kIdShift;
    // tab feature of tab@0 should be logged correctly.
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
                                       expected_metrics);
  }

  // Discard the first tab.
  content::WebContents* first_contents =
      browser()->tab_strip_model()->GetWebContentsAt(0);
  resource_coordinator::GetTabLifecycleUnitSource()
      ->GetTabLifecycleUnitExternal(first_contents)
      ->DiscardTab(LifecycleUnitDiscardReason::URGENT);

  // Switching to first tab logs a forgrounded event for test_urls_[0].
  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = {
        {TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 1},
        {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 1},
        {TabManager_Background_ForegroundedOrClosed::kLabelIdName,
         2 * kIdShift}};

    ukm_entry_checker_->ExpectNewEntry(kFOCEntryName, test_urls_[0],
                                       expected_metrics);
  }

  // Logs tab@1.
  LogTabFeaturesAt(1);
  {
    SCOPED_TRACE("");
    // tab feature of tab@1 should be logged correctly.
    UkmMetricMap expected_metrics = kBasicMetricValues;
    expected_metrics[TabManager_TabMetrics::kLabelIdName] = 4 * kIdShift;
    expected_metrics[TabManager_TabMetrics::kQueryIdName] = 3 * kIdShift;
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[1],
                                       expected_metrics);
  }

  // Discard the second tab.
  content::WebContents* second_content =
      browser()->tab_strip_model()->GetWebContentsAt(1);
  resource_coordinator::GetTabLifecycleUnitSource()
      ->GetTabLifecycleUnitExternal(second_content)
      ->DiscardTab(LifecycleUnitDiscardReason::URGENT);

  CloseBrowserSynchronously(browser());
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = {
        {TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 0},
        {TabManager_Background_ForegroundedOrClosed::kIsDiscardedName, 1},
        {TabManager_Background_ForegroundedOrClosed::kLabelIdName,
         4 * kIdShift}};

    ukm_entry_checker_->ExpectNewEntry(kFOCEntryName, test_urls_[1],
                                       expected_metrics);
  }
}

// Tests label_id is incremented if the LogOldestNTabFeatures is called second
// times without logging the label first.
IN_PROC_BROWSER_TEST_F(
    TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab,
    TabsAlreadyHaveLabelIdGetIncrementalLabelIds) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_urls_[0]));
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK));
  // No TabMetrics events are logged till now.
  EXPECT_EQ(0u, ukm_entry_checker_->NumEntries(kTabMetricsEntryName));

  // Logs tab@0.
  LogTabFeaturesAt(0);
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = kBasicMetricValues;
    expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName] = 2;
    expected_metrics[TabManager_TabMetrics::kLabelIdName] = 2 * kIdShift;
    expected_metrics[TabManager_TabMetrics::kQueryIdName] = 1 * kIdShift;
    // tab feature of tab@0 should be logged correctly.
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
                                       expected_metrics);
  }

  LogTabFeaturesAt(0);
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = kBasicMetricValues;
    expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName] = 2;
    expected_metrics[TabManager_TabMetrics::kLabelIdName] = 2 * kIdShift + 1;
    expected_metrics[TabManager_TabMetrics::kQueryIdName] = 3 * kIdShift;
    // tab feature of tab@0 should be logged correctly.
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[0],
                                       expected_metrics);
  }
}

class TabActivityWatcherPrerenderingTest : public TabActivityWatcherTest {
 public:
  TabActivityWatcherPrerenderingTest()
      : prerender_test_helper_(base::BindRepeating(
            &TabActivityWatcherPrerenderingTest::GetWebContents,
            base::Unretained(this))) {}
  ~TabActivityWatcherPrerenderingTest() override = default;

  void SetUp() override {
    prerender_test_helper_.SetUp(embedded_test_server());
    TabActivityWatcherTest::SetUp();
  }

  content::test::PrerenderTestHelper& prerender_helper() {
    return prerender_test_helper_;
  }

  content::WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetWebContentsAt(0);
  }

 private:
  content::test::PrerenderTestHelper prerender_test_helper_;
};

// Tests that all tabs metrics are logged with correct value and should not be
// logged in prerendering.
IN_PROC_BROWSER_TEST_F(TabActivityWatcherPrerenderingTest,
                       SwitchTabsWithPrerendering) {
  ukm::SourceId ukm_source_id_for_tab_0 = 0;
  ukm::SourceId ukm_source_id_for_activated_page = 0;

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_urls_[0]));
  EXPECT_EQ(0u, ukm_entry_checker_->NumEntries(kTabMetricsEntryName));

  // Load a page in the prerender.
  GURL prerender_url = embedded_test_server()->GetURL("/simple.html");
  prerender_helper().AddPrerender(prerender_url);

  // Adding a new foreground tab logs the previously active tab.
  ASSERT_TRUE(AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK));
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(
        kTabMetricsEntryName, test_urls_[0],
        {{TabManager_TabMetrics::kNavigationEntryCountName, 2}});

    ukm_source_id_for_tab_0 = ExpectNewEntryWithSourceId(
        test_urls_[0], kTabMetricsEntryName, 1,
        {{TabManager_TabMetrics::kNavigationEntryCountName, 2}});
  }

  // Switching to another tab logs the previously active tab.
  browser()->tab_strip_model()->ActivateTabAt(
      0, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, test_urls_[1],
                                       kBasicMetricValues);

    ExpectNewEntryWithSourceId(test_urls_[1], kTabMetricsEntryName, 2,
                               kBasicMetricValues);

    ExpectNewEntryWithSourceId(
        test_urls_[0], kFOCEntryName, 1,
        {{TabManager_Background_ForegroundedOrClosed::kIsForegroundedName, 1}},
        ukm_source_id_for_tab_0);
  }
  // Activating the prerendered page on the current active tab.
  prerender_helper().NavigatePrimaryPage(prerender_url);

  // Switching to another tab logs the previously active tab.
  browser()->tab_strip_model()->ActivateTabAt(
      1, {TabStripModel::GestureType::kOther});
  {
    SCOPED_TRACE("");
    UkmMetricMap expected_metrics = kBasicMetricValues;
    expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName] = 3;

    // A new entry should be added by the prerendering activation.
    ukm_entry_checker_->ExpectNewEntry(kTabMetricsEntryName, prerender_url,
                                       expected_metrics);

    ukm_source_id_for_activated_page = ExpectNewEntryWithSourceId(
        prerender_url, kTabMetricsEntryName, 3, expected_metrics);
  }

  // ukm::SourceId is updated when the prerendered page is activated.
  EXPECT_NE(ukm_source_id_for_tab_0, ukm_source_id_for_activated_page);
}

}  // namespace resource_coordinator
